<?php

/**
* xml-rpc value element wrapper
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcValue {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcValue($type=null,$value=null) {
		$this->__construct($type,$value);
	}/**/

	/**
	* "real" constructor. Sets type and value if provided
	* @param string $type
	* @param mixed $value
	* @access public
	*/
	function __construct($type=null,$value=null) {
		$this->setType($type);
		$this->setValue($value);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* sets value from ooXmlRpcNode instance
	* @param ooxmlrpcnode &$node
	* @access public
	* @return bool
	*/
	function setFromNode(&$node) {
		// checking input
		if (!is_a($node,'ooxmlrpcdomnode')) {
			return false;
		}
		//var_dump($node);
		// searching for type = value child element tagname
		if ($typeNodes=$node->getxpathnodes('child::*')) {
			$type=$typeNodes[0]->getTagName();
			$valueXpath='child::*';
			//$valueNodes=$node->getXpathNodes('child::*');
		} else {
			// no child element, default to string
			$type='string';
			$valueXpath='text()';
			//$valueNodes=$node->getXpathNodes('text()');
		}
		$this->type=$type;
		// getting value content node(s)
		$valueNodes=$node->getXpathNodes($valueXpath);
		// extracting value, method depends on type
		switch($type) {
			// non-scalars
			case 'array':
				$this->value=array();
				// sub-values are within <data>
				$valueNodes=$valueNodes[0]->getXpathNodes('data/*');
				// cycling values
				for ($n=0; $n<count($valueNodes); $n++) {
					// building instance
					$value=&new ooXmlRpcValue();
					// setting from node
					$value->setFromNode($valueNodes[$n]);
					// importing instance
					$this->value[]=&$value;
				}
				break;
			case 'struct':
				$this->value=array();
				// sub-values are within <data>
				$valueNodes=$valueNodes[0]->getXpathNodes('member');
				// cycling values (<member> nodes)
				for ($n=0; $n<count($valueNodes); $n++) {
					// building instance
					$value=&new ooXmlRpcValue();
					// getting key
					$subKeyNodes=$valueNodes[$n]->getXpathNodes('name');
					$key=$subKeyNodes[0]->getContent();
					// getting value
					$subValueNodes=$valueNodes[$n]->getXpathNodes('value');
					// setting from node
					$value->setFromNode($subValueNodes[0]);
					// importing instance
					$this->value[$key]=&$value;
				}
				break;
			// scalars
			case 'boolean':
				$value=$valueNodes[0]->getContent();
				if ($value=='1' or strtolower($value)=='true')
					$this->value=true;
				else $this->value=false;
				break;
			case 'i4':
			case 'int':
			case 'integer':
				$value=$valueNodes[0]->getContent();
				$this->type='int';
				$this->value=intval($value);
				break;
			case 'double':
				$value=$valueNodes[0]->getContent();
				//$this->type='int';
				settype($value,'double');
				$this->value=&$value;
				break;
			case 'string':
				//$value=$valueNodes[0]->getContent();
				//$this->value=&$value;
				//break;
			case 'base64':
				//$value=$valueNodes[0]->getContent();
				//$this->value=base64_decode($value);
				//$this->value=&$value;
				//break;
			case 'dateTime.iso8601':
				//$value=$valueNodes[0]->getContent();
				//break;
			default: // scalar
				$this->value=$valueNodes[0]->getContent();
		}
		return true;
		//var_dump($valueNodes);
	}/**/

	/**
	* check/tells if the value is valid
	* @param mixed $value optional, checks current value if omitted
	* @access public
	* @return bool
	*/
	function isValid($value=null) {
		if (is_null($value))
			$value=&$this->value;
		switch($this->type) {
			// scalars
			case 'boolean':
				return in_array($value,array(0,1));
			case 'string':
				return is_string($value);
			case 'int':
				return is_int($value);
			case 'double':
				return is_double($value);
			case 'base64':
				return ereg('[a-zA-Z0-9+=/]+',$value);
			case 'dateTime.iso8601':
				return ereg('[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$value);
			// non-scalars
			case 'array':
				return is_array($value);
			case 'struct':
				return is_array($value) or is_object($value);
			default:
				return false;
		}
	}/**/

	/**
	* check/tells if value is a scalar
	* @param mixed $value optional, checks current value if not provided
	* @access public
	* @return
	*/
	function isScalar($value=null) {
		if (is_null($value))
			$value=&$this->value;
		return is_scalar($value);
	}/**/

	/**
	* return value's type
	* @access public
	* @return string int|string|boolean|base64|double|dateTime.iso8601|struct|array
	*/
	function getType() {
		return $this->type;
	}/**/

	/**
	* sets type (value has to be null)
	* @param string $type
	* @access public
	* @return bool
	*/
	function setType($type) {
		if (!in_array($type,array('string','int','double','boolean','base64','dateTime.iso8601','struct','array'))) {
			return false;
		}
		if (!is_null($this->value)) {
			return false;
		}
		$this->type=$type;
		return true;
	}/**/

	/**
	* sets value (type has to be set first, else it will be guessed
	* @param mixed $value
	* @access public
	* @return bool
	*/
	function setValue($value) {
		if (is_null($value))
			return false;
		// if type is not set, try guessing
		if (is_null($this->type) and $guessed=$this->guessType($value)) {
			// echo "<p>guessed $guessed</p>";
			$this->setType($guessed);
		}
		if (!$this->isValid($value))
			return false;
		// scalar
		// $this->value=&$value;
		switch ($this->type) {
			// non-scalars
			case 'array':
			case 'struct':
				if (is_object($value))
					$value=get_object_vars($value);
				if (!is_array($value))
					return false;
				foreach ($value as $key=>$item) {
					if (is_a($item,'ooxmlrpcvalue')) {
						$this->value[$key]=$item;
					} else {
						$subValue=&new ooXmlRpcValue(null,$item);
						if ($subValue->setValue($item))
							$this->value[$key]=&$subValue;
					}
				}
				break;
			// scalars
			case 'boolean':
				if ($value) $this->value=1;
				else $this->value=0;
				break;
			case 'int':
			case 'double':
			case 'base64':
			case 'string':
			case 'dateTime.iso8601':
				$this->value=&$value;
				break;
			default:
				return false;
		}

		return true;
	}/**/

	/**
	* build and append self node(s) to parent node
	* @param ooXmlRpcDomNode &$parentNode
	* @access public
	* @return bool
	*/
	function appendToNode(& $parentNode) {
		// build & append value node
		$valueNode=&$parentNode->makeChild('value');
		// build & append type node to value node
		$typeNode=&$valueNode->makeChild($this->type);
		// insert content into type node
		switch ($this->type) {
			// non-scalars
			case 'array':
				//$arrayNode=&$typeNode->makeChild('array');
				// append data node
				$dataNode=&$typeNode->makeChild('data');
				// append child values to data node
				for ($n=0; $n<count($this->value); $n++) {
					$this->value[$n]->appendToNode($dataNode);
				}
				break;
			case 'struct':
				// cycling through sub-value nodes
				if (!is_array($this->value)) return false;
				foreach ($this->value as $key=>$value) {
					// appending <member> nodes
					$memberNode=&$typeNode->makeChild('member');
					// appending <name>
					$nameNode=&$memberNode->makeChild('name');
					$nameNode->setContent($key);
					// appending <value>
					//$deb=&wak::getservice('wakdebug');
					//echo $deb->vardump($value);
					$value->appendToNode($memberNode);
				}
				break;
			//scalars
			default:
				$typeNode->setContent($this->value);
		}
		return true;
	}/**/

	/**
	* tries to guess a xml-rpc type for the var
	* @param
	* @access public
	* @return
	*/
	function guessType(&$var) {
		if (is_bool($var))
			return 'boolean';
		if (is_int($var))
			return 'int';
		if (is_double($var))
			return 'double';
		if (is_object($var))
			return 'struct';
		if (is_array($var)) {
			if (is_int(key($var)))
				return 'array';
			else return 'struct';
		}
		if (is_string($var) and ereg('[0-9]{6}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$var))
			return 'dateTime.iso8601';
		if (is_string($var) and ereg('^[a-zA-Z0-9+/=]{50,}$',$var))
			return 'base64';
		if (is_string($var))
			return 'string';
		
		return false;
	}/**/

	/**
	* Return value
	* @access public
	* @return mixed
	*/
	function getValue() {
		if (!is_array($this->value))
				return $this->value;
		// recursion to get sub-values
		$value=array();
		foreach (array_keys($this->value) as $key) {
			$value[$key]=$this->value[$key]->getValue();
		}
		return $value;
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var type desc
	*/
	var $type; // int|boolean|string|base64|dateTime.iso8601|struct|array
	var $value;
}
?>
